import json
from decimal import Decimal
from time import sleep

from django.db import transaction
from django.http import JsonResponse
from django.shortcuts import render

# Create your views here.
from django.utils import timezone
from django.views import View
from django_redis import get_redis_connection

from apps.goods.models import SKU
from apps.orders.models import OrderInfo, OrderGoods
from apps.users.models import Address
from utils.views import LoginRequireJSONMixin

"""
需求:
    提交订单页面的展示
前端:
    发送一个axios请求来获取地址信息和购物车中选中商品的信息
后端:
    请求:
        必须是登录用户才可以访问
    业务逻辑:
        地址信息和商品信息
    响应:
        JSON
        
    路由:
        GET orders/settlement/
    
    步骤:
        # 1. 获取用户信息
        # 2. 地址信息
        #     2.1 查询用户的地址信息
        #     2.2 将对象数据转换为字典数据
        # 3. 购物车中选中的商品信息
        #     3.1 链接redis
        #     3.2 hash
        #     3.3 set
        #     3.4 重新组织一个选中的信息
        #     3.5 根据商品的id查询商品的具体信息
        #     3.6 将对象数据转换为字典数据

"""


class OrderSettlementView(LoginRequireJSONMixin, View):

    def get(self, request):
        # 1. 获取用户信息
        user = request.user

        # 2. 地址信息
        #     2.1 查询用户的地址信息
        addresses = Address.objects.filter(is_deleted = False)
        #     2.2 将对象数据转换为字典数据
        addresses_list = []
        for address in addresses:
            addresses_list.append({
                'id': address.id,
                'province': address.province.name,
                'city': address.city.name,
                'district': address.district.name,
                'place': address.place,
                'receiver': address.receiver,
                'mobile': address.mobile
            })

        # 3. 购物车中选中的商品信息
        #     3.1 链接redis
        redis_cli = get_redis_connection('carts')
        pipeline = redis_cli.pipeline()
        #     3.2 hash
        pipeline.hgetall('carts_%s' % user.id)
        #     3.3 set
        pipeline.smembers('selected_%s' % user.id)
        result = pipeline.execute()

        sku_id_counts = result[0]
        selected_ids = result[1]

        #     3.4 重新组织一个选中的信息
        selected_carts = {}
        for sku_id in selected_ids:
            selected_carts[int(sku_id)] = int(sku_id_counts[sku_id])

        #     3.5 根据商品的id查询商品的具体信息
        sku_list = []
        for sku_id, count in selected_carts.items():
            sku = SKU.objects.get(pk=sku_id)
        #     3.6 将对象数据转换为字典数据
            sku_list.append({
                'id': sku.id,
                'name': sku.name,
                'default_image_url': sku.default_image.url,
                'count': count,
                'price': sku.price
            })

        context = {
            'skus': sku_list,
            'addresses': addresses_list,
            'freight': 10
        }

        return JsonResponse({'code': 0, 'errmsg': 'ok', 'context': context})


"""
需求:
    点击提交订单，生成订单
前端:
    会发送axios请求，POST 携带数据
后端:
    请求:
        接受请求，接收参数
    业务逻辑:
        数据入库
    响应:
        返回响应
    
    路由:POST
    步骤:
        # 1. 接受请求
        #     user， address_id, pay_method
        # 2. 验证数据
        #     order_id(自己生成主键)
        #     支付状态由支付方式决定
        #     总数量，总金额 = 0
        #     运费
        #     
        # 3. 数据入库
        #     3.1 先保存订单基本信息
        #     
        #     3.2 再保存订单商品信息
        #         a. 链接redis
        #         b. 获取hash
        #         c. 获取set
        #         d. 遍历选中商品的id
        #             最好重写组织一个数据，这个数据是选中商品的信息{sku_id: count, sku_id: count}
        #         e. 遍历 根据选中商品的id进行查询
        #         f. 判断库存是否筹组
        #         g. 如果不充足，则下单失败
        #         h. 如果充足则库存减少，销量增加
        #         i. 累加总数量和总金额
        #         j. 保存订单商品信息
        #     3.3 更新订单的总金额和总数量
        #     3.4 将redis中的选中商品的信息移除
        #     
        # 4. 返回响应
    
"""


class OrderCommitView(LoginRequireJSONMixin, View):

    def post(self, request):
        # 1. 接受请求
        #     user， address_id, pay_method
        user = request.user

        data = json.loads(request.body.decode())
        address_id = data.get('address_id')
        pay_method = data.get('pay_method')

        # 2. 验证数据
        if not all([address_id, pay_method]):
            return JsonResponse({'code': 400, 'errmsg': '参数不全'})

        try:
            address = Address.objects.get(id=address_id)
        except Address.DoesNotExist:
            return JsonResponse({'code': 400, 'errmsg': '参数不全'})

        if pay_method not in [OrderInfo.PAY_METHODS_ENUM['CASH'], OrderInfo.PAY_METHODS_ENUM['ALIPAY']]:
            return JsonResponse({'code': 400, 'errmsg': '参数不正确'})

        #     order_id(自己生成主键) 格式:年月日时分秒+9位用户id
        order_id = timezone.localtime().strftime('%Y%m%d%H%M%S') + "%09d" % user.id

        #     支付状态由支付方式决定
        # if pay_method == 1: # 货到付款
        #     pay_status = 2
        # else:
        #     pay_status = 1
        if pay_method == OrderInfo.PAY_METHODS_ENUM['CASH']:
            status = OrderInfo.ORDER_STATUS_ENUM['UNSEND']
        else:
            status = OrderInfo.ORDER_STATUS_ENUM['UNPAID']

        # 总数量，总金额 = 0
        total_count = 0
        total_amount = Decimal('0')
        # 运费
        freight = Decimal('10.00')

        with transaction.atomic():

            # 事务开始点
            point = transaction.savepoint()
            # 3. 数据入库
            # 3.1 先保存订单基本信息
            order_info = OrderInfo.objects.create(
                order_id=order_id,
                user=user,
                address=address,
                total_count=total_count,
                total_amount=total_amount,
                freight=freight,
                pay_method=pay_method,
                status=status
            )

            # 3.2 再保存订单商品信息
            # a. 链接redis
            redis_cli = get_redis_connection('carts')
            # b. 获取hash
            sku_id_counts = redis_cli.hgetall('carts_%s' % user.id)
            # c. 获取set
            selected_ids = redis_cli.smembers('selected_%s' % user.id)
            # d. 遍历选中商品的id
            carts = {}
            # 最好重写组织一个数据，这个数据是选中商品的信息{sku_id: count, sku_id: count}
            for sku_id in selected_ids:
                carts[int(sku_id)] = int(sku_id_counts[sku_id])

            # e. 遍历 根据选中商品的id进行查询
            for sku_id, count in carts.items():
                for i in range(10):
                    sku = SKU.objects.get(id=sku_id)

                    # f. 判断库存是否筹组
                    # g. 如果不充足，则下单失败
                    if sku.stock < count:

                        # 回滚点
                        transaction.savepoint_rollback(point)

                        return JsonResponse({'code': 400, 'errmsg': "库存不足"})

                    # h. 如果充足则库存减少，销量增加
                    # sku.stock -= count
                    # sku.sales += count
                    # sku.save()

                    # 1. 先记录一个数据
                    # 久库存我么参照这个记录
                    old_stock = sku.stock

                    # 2. 更新的时候，再比对一下这个记录对不对
                    new_stock = sku.stock - count
                    new_sales = sku.sales + count

                    results = SKU.objects.filter(id=sku_id, stock=old_stock).update(stock=new_stock, sales=new_sales)

                    if results == 0:
                        sleep(0.005)
                        continue
                        # 暂时回滚和下单失败
                        # transaction.savepoint_rollback(point)
                        # return JsonResponse({'code':400, 'errmsg': "下单失败"})

                    # i. 累加总数量和总金额
                    order_info.total_count += count
                    order_info.total_amount += (count * sku.price)

                    # j. 保存订单商品信息
                    OrderGoods.objects.create(
                        order = order_info,
                        sku=sku,
                        count=count,
                        price=sku.price
                    )

                    break

            order_info.save()

            # 事务的提交点
            transaction.savepoint_commit(point)
        # 3.3 更新订单的总金额和总数量
        # 3.4 将redis中的选中商品的信息移除

        # 4. 返回响应
        return JsonResponse({'code':0, 'errmsg': 'ok', 'order_id': order_id})


"""
解决并发的超卖问题:
①队列
②锁
    悲观锁：当查询某条记录时，即让数据库为该记录加锁，锁住记录后别人无法操作，使用类似如下语法
          悲观锁类似于我们在多线程资源竞争时添加的互斥锁，容易出现死锁现象，采用不多。
          
    乐观锁：乐观锁并不是真实存在的锁，而是在更新的时候判断此时的库存是否是之前查询出的库存，如果相同，表示没人修改，可以更新库存，否则表示别人抢过资源，不再执行库存更新
    
    
    步骤：
        # 1. 先记录一个数据
        # 2. 更新的时候，再比对一下这个记录对不对

"""
